package model;

import util.Config;
import util.Util;

import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.awt.*;

public class Model {

    public static final double EPS = 1e-4;

    private final Map<Integer, int[]> images = new ConcurrentHashMap<Integer, int[]>();

    private final Map<Integer, int[]> sketches = new ConcurrentHashMap<Integer, int[]>();

    private final Map<Integer, Boolean> editImages = new ConcurrentHashMap<Integer, Boolean>();

    private GUIListener gl;

    private SynchronizerListener sl;

    private final static Random RANDOM = new Random();

    public int[] getImage(int imageID) {
        if (!images.containsKey(imageID)) {
            sl.getImage(imageID);
        }
        try {
            while (!images.containsKey(imageID)) {
                Thread.sleep(Config.getInstance().getSleepingTime());
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return images.get(imageID);
    }

    public int createImage() {
        int imageID = RANDOM.nextInt((int) (System.nanoTime() / 1e10));
        editImages.put(imageID, Boolean.valueOf(true));
        Config config = Config.getInstance();
        int[] image = new int[config.getImageWidth() * config.getImageHeight() + 1];
        images.put(imageID, image);
        sketches.put(imageID, generateSketch(image));
        sl.addImage(imageID);
        return imageID;
    }

    private int[] generateSketch(int[] a) {
        Config config = Config.getInstance();
        BufferedImage image = Util.getBufferedImage(a, config.getImageWidth(), config.getImageHeight());
        int width = config.getSketchWidth();
        int height = config.getSketchHeight();
        Image sketch = image.getScaledInstance(width, height, 0);
        int[] res = new int[width * height + 1];
        PixelGrabber pg = new PixelGrabber(sketch, 0, 0, width, height, res, 0, width);
        try {
            if (!pg.grabPixels()) throw new RuntimeException("I haven't grabbed yet!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    public void setImage(int imageID, int[] image) {
        images.put(imageID, image);
        sketches.put(imageID, generateSketch(image));
    }

    public void imageChangeFromGUI(int imageID, int x, int y, int dx, int dy) {
        drawLine(imageID, x, y, dx, dy);
        sl.imageChange(imageID, x, y, dx, dy);
    }

    public void imageChangeFromSyncronizer(int imageID, int x, int y, int dx, int dy) {
        if (images.containsKey(imageID)) {
            drawLine(imageID, x, y, dx, dy);
            gl.imageChange(imageID);
        } else {
            System.out.println("There is not image " + imageID);
        }
    }

    public int[] getSketch(int imageID) {
        try {
            while (!sketches.containsKey(imageID)) {
                Thread.sleep(Config.getInstance().getSleepingTime());
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return sketches.get(imageID);
    }

    public void addSketch(int imageID, int[] sketch) {
        sketches.put(imageID, sketch);
        gl.addSketch(imageID);
    }

    public void removeImage(int imageID) {
        System.out.println("Remove image " + imageID);
        images.remove(imageID);
        sketches.remove(imageID);
        gl.removeImage(imageID);
    }

    private void drawLine(int imageID, int x, int y, int dx, int dy) {
        int[] res = images.get(imageID);
        double stepX = EPS * dx;
        double stepY = EPS * dy;
        for (int i = 0; i < 1 / EPS; i++) {
            res[getNumber((int) (x + i * stepX), (int) (y + i * stepY))] = Config.getInstance().getColor();
        }
        sketches.put(imageID, generateSketch(res));
        gl.imageChange(imageID);
    }

    private int getNumber(int x, int y) {
        return y * Config.getInstance().getImageWidth() + x;
    }

    public void addEditImage(int imageID) {
        editImages.put(imageID, true);
        images.remove(imageID);
        getImage(imageID);
        gl.imageChange(imageID);
        sl.joinImage(imageID);
    }

    public boolean isEditable(int imageID) {
        return editImages.containsKey(imageID);
    }

    public void setGUIListener(GUIListener listener) {
        gl = listener;
    }

    public void setSynchronizerListener(SynchronizerListener listener) {
        sl = listener;
    }

}